package org.stars.daostars.core;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.dao.exception.DaoException;
import org.stars.daostars.ConstraintOperator;
import org.stars.daostars.SQLExceptionTranslator;
import org.stars.daostars.ParameterConverter;
import org.stars.daostars.operators.Empty;
import org.stars.daostars.operators.Equals;
import org.stars.daostars.operators.Null;
import org.stars.daostars.operators.ValidId;
import org.stars.daostars.sqlmapper.SqlMapper;
import org.stars.daostars.sqlmapper.loader.xml.DaoLoader;
import org.stars.util.FileUtility;
import org.stars.util.StringUtility;
import org.stars.util.diagnostic.StackTraceDiagnosticHandler;
import org.stars.util.jar.FoundJarResourceEventHandler;
import org.stars.util.jar.JarResourceUtility;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;

import org.stars.daostars.core.parser.FindIncludedQuery;
import org.xml.sax.SAXException;

/**
 * Contenitore per tutte le query dei dao. Tutte le query dei vari dao vengono
 * caricate in fase di startup dell'applicaizione. All'interno di questa classe
 * troviamo una hashtable la cui chiave contiene i nomi dei vari Dao e come
 * oggetto contiene un'altra hashtable che a sua volta contiene i nomi di tutte
 * le query e le loro definizioni.
 * <p>
 * Per default le query vengono caricate da una cartella sotto
 * <code>WEB-INF</code> chiamata <code>daosql</code>.
 * <p>
 * Per ogni Dao deve esistere un file di tipo xml con la definizione del codice
 * sql.
 * <p>
 * Ad esempio per il DaoPersona esisterà il file <code>DaoPersona.xml</code>
 * <p>
 * I nomi dei dao e delle query sono case insensitive e vengono messi tutti in
 * maiuscolo.
 * <p>
 * 
 * @author Francesco Benincasa (908099)
 * @version 1.0.0, 11/08/2006
 * 
 */
public class DaoStars implements Serializable {

	/**
	 * Aggiunge un profilo. Nel caso in cui non esista un profilo attivo, questo
	 * viene inserito pure come profilo attivo.
	 * 
	 * @param profile
	 *            profilo da inserire
	 * @return <code>true</code>
	 */
	public boolean addProfile(Profile profile) {
		String pName = profile.getName();
		pName = pName.toUpperCase();

		profile.setName(pName);
		profileSet.put(pName, profile);

		if (activeProfile == null) {
			activeProfile = profile;
		}

		return true;
	}
	
	/**
	 * Recupera il logger
	 * @return
	 * 	logger
	 */
	protected Log getLog()
	{
		return LogFactory.getLog(DaoStars.class);
	}

	/**
	 * elenco dei profili caricati per daostars
	 */
	protected Hashtable<String, Profile> profileSet;

	/**
	 * nome del profilo attivo
	 */
	protected Profile activeProfile;

	/**
	 * versione della libreria
	 */
	protected static String version = "1.5.0";
	
	
	/**
	 * Version of the dao
	 * 
	 * @return version
	 */
	public static String getVersion() {
		return version;
	}

	/**
	 * serial id
	 */
	private static final long serialVersionUID = -5710299763662278069L;

	/**
	 * set di operatori.
	 */
	private Hashtable<String, ConstraintOperator> operatorSet;

	/**
	 * Imposta l'operatore.
	 * 
	 * @param operator
	 *            operatore
	 */
	public void setOperator(ConstraintOperator operator) {
		/*
		 * recuperiamo la key dell'operator, che è lo stesso nome della classe
		 * senza package.
		 */
		String key = operator.getClass().getName();

		key = key.substring(key.lastIndexOf(".") + 1);
		key = key.toUpperCase();

		operatorSet.put(key, operator);
	}

	/**
	 * dato il nome di un operatore, restituisce la sua implementazione
	 * 
	 * @param key
	 *            chiave dell'operatore
	 * @return operatore
	 */
	public ConstraintOperator getOperator(String key) {
		key = StringUtility.nvl(key.toUpperCase());

		return operatorSet.get(key);
	}


	/**
	 * Costruttore. Inizializza l'hashtable delle query.
	 */
	public DaoStars() {
		reset();
	}

	/**
	 * Effettua l'inizializzazione del DaoSql. Come situazione di default, esso
	 * inizializza un unico profilo con il nome di DEFAULT e lo imposta come
	 * quello attivo.
	 */
	public void reset() {
		operatorSet = new Hashtable<String, ConstraintOperator>();

		// creiamo il profilo di default
		activeProfile = null;
		profileSet = new Hashtable<String, Profile>();
		//DaoProfile defaultProfile = new DaoProfile();
		
		//String defaulProfileName = "DEFAULT";
		//defaultProfile.setName(defaulProfileName);
		//addProfile(defaultProfile);
		

		setOperator(new Empty());
		setOperator(new Equals());
		setOperator(new Null());
		setOperator(new ValidId());
	}

	/**
	 * Numero di file di definizione sql trovati ed analizzati.
	 * 
	 * @return numeri di file xml contenenti la definizione delle query
	 */
	public int size() {
		return activeProfile.getSqlMapperSet().size();
	}

	/**
	 * Recupera dato un Dao ed una query la classe che rappresenta quest'ultima.
	 * Il nome del dao deve corrispondere alla definizione dello stesso. Ad
	 * esempio, se un dao è definito nel file <code>DaoFunction.xml</code>,
	 * per recuperare le query di tale definizione occorrerà passare come primo
	 * parametro <code>DaoFunction</code>.
	 * 
	 * Nel caso la query non esiste, viene restituito un oggetto nullo.
	 * 
	 * @param sDao
	 *            nome del dao completo (ad esempio <code>DaoFunction</code>).
	 * @param sQueryName
	 *            nome della query da ottenere
	 * @return query sql oppure stringa vuota
	 */
	public String getQuerySql(String sDao, String sQueryName) {
		SqlMapper definition;
		sDao = sDao.toUpperCase();
		if (activeProfile.getSqlMapperSet().containsKey(sDao)) {
			definition = activeProfile.getSqlMapperSet().get(sDao);
			sQueryName = sQueryName.toUpperCase();

			if (definition.getQuerySet().containsKey(sQueryName)) {

				return definition.getQuery(sQueryName).getSql();
			}
		}
		return "";
	}

	/**
	 * Dato una classe di un entity bean, ed una query definita per il dao
	 * associato, viene recuperato il codice sql che definisce la query stessa.
	 * Il nome della query è case insensitive. Nel caso in cui il dao non sia
	 * stato definito o la query non esista, viene restituita una stringa vuota.
	 * 
	 * @param ebClass
	 *            entity bean associato ad un dao
	 * @param sQueryName
	 *            nome della query
	 * @return codice della query
	 */
	public String getQuerySql(Class<?> ebClass, String sQueryName) {
		SqlMapper definition;
		String sDao = SqlUtility.getDaoName(ebClass);
		sDao = sDao.toUpperCase();
		if (activeProfile.getSqlMapperSet().containsKey(sDao)) {
			definition = activeProfile.getSqlMapperSet().get(sDao);
			sQueryName = sQueryName.toUpperCase();

			if (definition.getQuerySet().containsKey(sQueryName)) {
				return definition.getQuery(sQueryName).getSql();
			}
		}
		return "";
	}

	/**
	 * Recupera dato un Dao ed una query la classe che rappresenta quest'ultima.
	 * Il nome del dao deve corrispondere alla definizione dello stesso. Ad
	 * esempio, se un dao è definito nel file <code>DaoFunction.xml</code>,
	 * per recuperare le query di tale definizione occorrerà passare come primo
	 * parametro <code>DaoFunction</code>.
	 * 
	 * Nel caso la query non esiste, viene restituito un oggetto nullo.
	 * 
	 * @param sDao
	 *            nome del dao completo (ad esempio <code>DaoFunction</code>).
	 * @param sQueryName
	 *            nome della query da ottenere
	 * @return oggetto rappresentante la query del dao
	 */
	public SqlQuery getQuery(String sDao, String sQueryName) {
		sDao = sDao.toUpperCase();

		SqlMapper dao = getSqlMapper(sDao);
		if (dao == null)
			return null;

		sQueryName = sQueryName.toUpperCase();

		SqlQuery query = dao.getQuery(sQueryName);
		if (query == null)
			return null;

		return query;
	}

	/**
	 * Recupera una query di un determinato dao. Nel caso la query non esiste,
	 * viene restituita un array nullo.
	 * <p>
	 * 
	 * @param sDao
	 *            nome del dao.
	 * @param sQueryName
	 *            nome della query da ottenere
	 * @return elenco dei parametri o null nel caso non esiste
	 */
	public List<SqlParameter> getParameters(String sDao,
			String sQueryName) {
		sDao = sDao.toUpperCase();

		SqlMapper dao = getSqlMapper(sDao);

		if (dao == null)
			return null;

		sQueryName = sQueryName.toUpperCase();
		SqlQuery daoQuery = dao.getQuery(sQueryName);

		if (daoQuery == null)
			return null;

		return daoQuery.getParameters();
	}

	/**
	 * Recupera una definizione di dao a partire dal suo nome. Per un DAO
	 * definito in <code>DaoPersona.xml</code> ad esempio si dovrà utilizzare
	 * il nome <code>DaoPersona</code>.
	 * 
	 * @param sDaoName
	 *            nome del dao
	 * @return oggetto che rappresenta la definizione del Dao oppure null
	 */
	public SqlMapper getSqlMapper(String sDaoName) {
		sDaoName = sDaoName.toUpperCase();

		if (activeProfile.getSqlMapperSet().containsKey(sDaoName)) {

			return activeProfile.getSqlMapperSet().get(sDaoName);

		}
		return null;
	}

	/**
	 * Unica istanza del DaoSql.
	 */
	private static DaoStars instance;

	/**
	 * Getter dell'istanza.
	 * 
	 * @return oggetto DaoSql
	 */
	public static DaoStars getInstance() {
		if (instance == null) {
			instance = new DaoStars();
		}
		return instance;
	}

	/**
	 * Getter dell'attributo defaultConnectionName.
	 * 
	 * @return the defaultConnectionName
	 */
	public String getDefaultConnectionName() {
		return activeProfile.getDefaultConnectionName();
	}


	/**
	 * Getter dell'attributo parameterConverter
	 * 
	 * @return the parameterConverter
	 */
	public ParameterConverter getParameterConverter() {
		return activeProfile.getParameterConverter();
	}

	/**
	 * Setter dell'attributo parameterConverter.
	 * 
	 * @param value
	 *            the parameterConverter to set
	 */
	public void setParameterConverter(ParameterConverter value) {
		activeProfile.setParameterConverter(value);
	}

	/**
	 * @return the operatorSet
	 */
	public Hashtable<String, ConstraintOperator> getOperatorSet() {
		return operatorSet;
	}

	/**
	 * @param operatorSet
	 *            the operatorSet to set
	 */
	public void setOperatorSet(Hashtable<String, ConstraintOperator> operatorSet) {
		this.operatorSet = operatorSet;
	}

	/**
	 * @return the stackTraceDiagnosticHandler
	 */
	public StackTraceDiagnosticHandler getStackTraceDiagnosticHandler() {
		return activeProfile.getStackTraceDiagnosticHandler();
	}
	

	/**
	 * @return the exceptionTranslator
	 */
	public SQLExceptionTranslator getSQLExceptionTranslator() {
		return activeProfile.getSQLExceptionTranslator();
	}
	
	/**
	 * @param exceptionTranslator the exceptionTranslator to set
	 */
	public void setSQLExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
		activeProfile.setSQLExceptionTranslator(exceptionTranslator);
	}	


	/**
	 * @param stackTraceDiagnosticHandler
	 *            the stackTraceDiagnosticHandler to set
	 */
	public void setStackTraceDiagnosticHandler(
			StackTraceDiagnosticHandler stackTraceDiagnosticHandler) {
		activeProfile.setStackTraceDiagnosticHandler(stackTraceDiagnosticHandler);
	}

	/**
	 * Getter dell'attributo activeProfile
	 * 
	 * @return the activeProfile
	 */
	public Profile getActiveProfile() {
		return activeProfile;
	}

	/**
	 * path di base dalla quale partono tutti gli altri percorsi.
	 */
	private String basePath;

	/**
	 * Getter dell'attributo basePath
	 * 
	 * @return the basePath
	 */
	public String getBasePath() {
		return basePath;
	}

	/**
	 * Setter dell'attributo basePath
	 * 
	 * @param basePath
	 *            the basePath to set
	 */
	public void setBasePath(String basePath) {
		this.basePath = basePath;
	}

	/**
	 * Imposta il profilo attivo in base al nome. Il profilo deve essere già
	 * presente nel set dei profili. Se il nome non è presente nel set il
	 * profilo attivo viene impostato a <code>null</code>.
	 * 
	 * @param activeProfileName
	 *            nome del profilo.
	 * 
	 */
	public void setActiveProfile(String activeProfileName) {
		activeProfileName = activeProfileName.toUpperCase();
		activeProfile = profileSet.get(activeProfileName);
	}

	/**
	 * A seguito del profilo attivo, questo metodo provvede a caricare le
	 * definizioni dei dao associati al profilo attuale.
	 * @throws DaoException 
	 * @throws SAXException 
	 * @throws ParserConfigurationException 
	 */
	public void loadActiveProfile() throws DaoException, ParserConfigurationException, SAXException {
		/*
		 * Una volta caricati tutti i profili e definito quello attivo,
		 * carichiamo questo ultimo.
		 * 
		 * Carichiamo tutti i source presenti nel profilo.
		 */
		Profile active = DaoStars.getInstance().getActiveProfile();
		
		if (active==null)
		{
			String msg="It is impossibile to define the active profile!";			
			throw(new DaoException(msg));
		}
		
		Log log=getLog();
		
		log.info("The active profile is "+active.getName().toUpperCase());
		//	visualizziamo le informazioni relative al profilo attivo
		log.info("The parameter converter used is "+active.getParameterConverter().getClass().getName());
		log.info("The stack trace diagnostic handler used is "+active.getStackTraceDiagnosticHandler().getClass().getName());
		log.info("The stack trace diagnostic is "+(active.getStackTraceDiagnosticHandler().isEnabled() ? "ENABLED": "DISABLED"));
		
		DaoLoader loader = new DaoLoader(active);

		String path;

		for (Source src : active.getDaoSource()) {
			// caso di caricamento da directory
			if (src.getJar().length() == 0 && src.getFilePattern().endsWith("*.xml")) {
				// ASSERT: siamo su file system
				path = basePath + "/" + src.getDirectory();
				path = FileUtility.adjustPath(path);
				loader.loadFromDirectory(path);
			} else if (src.getJar().length() > 0 && src.getFilePattern().endsWith("*.xml")) {
                JarResourceHandler jrh=new JarResourceHandler();
                jrh.loader=loader;
                
				JarResourceUtility.findResources(src.getJar(), src.getDirectory(), src.getFilePattern(), jrh);
			}
		}
		
		// ora proviamo a risolvere per ogni daoDefinition tutti gli include che troviamo
		try {
			FindIncludedQuery.execute(active);
			log.debug("Parsing of the included queries finished.");
		} catch (Exception e) {
			log.error("Error during parsing of the included queries.");
		}
	}
	
	// This filter only returns file
	private class JarResourceHandler implements FoundJarResourceEventHandler {
		public DaoLoader loader;
		public void onGetJarEvent(String jarName, String fileName, InputStream inputStream) {
			loader.loadFromInputStream(fileName, inputStream);
		}
				
	};

}
